package com.wanwu.channel.remote.allinpay.http;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.wanwu.channel.common.enums.ExceptionEnum;
import com.wanwu.channel.common.enums.TradeStatusEnum;
import com.wanwu.channel.common.exception.SystemException;
import com.wanwu.channel.common.util.*;
import com.wanwu.channel.dao.entity.ChannelMerchantInfoEntity;
import com.wanwu.channel.remote.allinpay.config.TongLianConfig;
import com.wanwu.channel.remote.allinpay.http.req.*;
import com.wanwu.channel.remote.allinpay.http.resp.DeclareQryRespBody;
import com.wanwu.channel.remote.allinpay.http.resp.DeclareQryRespHead;
import com.wanwu.channel.remote.allinpay.http.resp.DeclareReSendRespBody;
import com.wanwu.channel.remote.allinpay.http.resp.TlDeclareHttpResp;
import com.wanwu.channel.remote.wxpay.util.MD5Util;
import jodd.http.HttpResponse;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName TonglianDeclareHttp
 * @Description 通联报关http交互类
 * @Author husheng
 * @Date 2020-01-21 10:18
 */
@Component
public class TonglianDeclareHttp {

    private static Logger logger = LoggerFactory.getLogger(TonglianDeclareHttp.class);

    @Resource
    private TongLianConfig tongLianConfig;

    /**
     * 通联报关
     * @param reqDto
     * @return
     */
    public TlDeclareHttpResp requestTongLian(TlDeclareReq reqDto){
        ChannelMerchantInfoEntity entity = reqDto.getChannelMerchantInfoEntity();
        String mchtId = null == entity ? tongLianConfig.getMchtId() : entity.getDeclareMchtId();
        String declareKey = null == entity ? tongLianConfig.getDeclareKey() : entity.getDeclareKey();
        PaymentInfoReq req = new PaymentInfoReq();
        TlDeclareHttpResp response = new TlDeclareHttpResp();
        try {
            //1.设置请求头信息
            TlDeclareHead head = buildTlDeclareHead(reqDto.getChannelNo(), mchtId);
            //先放入请求body
            req.setBodyReqDto(reqDto.getHttpReq());
            //2.签名
            String bodyXml = XmlUtil.toXml(reqDto.getHttpReq());
            bodyXml = bodyXml.replace(" ","");
            String sign = MD5Util.MD5Encode(bodyXml + "<key>" + declareKey + "</key>", "UTF-8").toUpperCase();
            head.setSignMsg(sign);
            //签名完塞入head然后设置值
            req.setHeadDto(head);
            //3.生成paramsValue
            String reqXml = XmlUtil.toXml(req);
            reqXml = reqXml.replace(" ","");
            String param = Base64.encode(reqXml.getBytes(StandardCharsets.UTF_8));
            //base64编码中的加号替换成%2B
            param = param.replace("+", "%2B");
            //LogUtil.info(logger,"通联报关请求报文【{0}】", reqXml);
            LogUtil.info(logger,"通联报关请求伪报文【{0}】", req.toString());
            String result = doPost(tongLianConfig.getDeclareUrl(), param);
            // 进行返回测试mock
            //String result = mockTest(req);
            LogUtil.info(logger,"通联报关请求返回结果【{0}】",result);
            PaymentInfoResp resp = XmlUtil.toBean(result, PaymentInfoResp.class);
            //验签
            String respSign = resp.getHeadDto().getSignMsg();
            String respBodyXml = result.substring(result.indexOf("<BODY>"), result.indexOf("</BODY>")+7);
            String localSign = MD5Util.MD5Encode(respBodyXml + "<key>" + declareKey + "</key>", "UTF-8").toUpperCase();
            if(!respSign.equals(localSign)){
                throw new SystemException(ExceptionEnum.TL1010, "通联报关返回报文验签失败", TradeStatusEnum.UNKNOWN);
            }
            response = resp.getBodyRespDto();
        }catch (Exception e){
            if(e instanceof SystemException){
                SystemException ex = (SystemException)e;
                response.setReturnCode(ex.getCode());
                response.setReturnMsg(ex.getMsg());
            }else{
                response.setReturnCode(ExceptionEnum.E10000.name());
                response.setReturnMsg(ExceptionEnum.E10000.getMsg());
            }
        }
        return response;
    }

    /**
     * 报关重新发送
     * @param reqBody
     * @return
     */
    public DeclareReSendRespBody reSendDeclare(DeclareReSendReqBody reqBody, ChannelMerchantInfoEntity entity){
        DeclareReSendReq req = new DeclareReSendReq();
        DeclareReSendRespBody response = new DeclareReSendRespBody();
        try {
            String declareKey = null == entity ? tongLianConfig.getDeclareKey() : entity.getDeclareKey();
            //1.设置请求头信息
            DeclareReSendHead head = new DeclareReSendHead();
            head.setCharset("1");
            head.setSignMsg("1");
            head.setSignType("1");
            head.setTransDatetime(DateUtils.get(8,new Date()));
            head.setVersion("v5.3");
            //先放入请求body
            req.setReSendReqBody(reqBody);
            //2.签名
            String bodyXml = XmlUtil.toXml(reqBody);
            String sign = MD5Util.MD5Encode(bodyXml + "<key>" + declareKey + "</key>", "UTF-8").toUpperCase();
            head.setSignMsg(sign);
            //签名完塞入head然后设置值
            req.setReSendReqHead(head);
            //3.生成paramsValue
            String reqXml = XmlUtil.toXml(req);
            String param = Base64.encode(reqXml.getBytes(StandardCharsets.UTF_8));
            //base64编码中的加号替换成%2B
            param = param.replace("+", "%2B");
            LogUtil.info(logger,"重新发送报关请求报文【{0}】", reqXml);
            String result = doPost(tongLianConfig.getDeclareQryUrl(), param);
            LogUtil.info(logger,"重新发送报关请求返回结果【{0}】",result);
            DeclareReSendResp resp = XmlUtil.toBean(result, DeclareReSendResp.class);
            //验签
            String respSign = resp.getReSendRespHead().getSignMsg();
            String respBodyXml = result.substring(result.indexOf("<BODY>"), result.indexOf("</BODY>")+7);
            String localSign = MD5Util.MD5Encode(respBodyXml + "<key>" + declareKey + "</key>", "UTF-8").toUpperCase();
            if(!respSign.equals(localSign)){
                throw new SystemException(ExceptionEnum.TL1010, "通联重新报关返回报文验签失败", TradeStatusEnum.UNKNOWN);
            }
            response = resp.getReSendRespBody();
        }catch (Exception e){
            if(e instanceof SystemException){
                SystemException ex = (SystemException)e;
                response.setReturnCode(ex.getCode());
                response.setReturnMsg(ex.getMsg());
            }else{
                response.setReturnCode(ExceptionEnum.E10000.name());
                response.setReturnMsg(ExceptionEnum.E10000.getMsg());
            }
        }
        return response;
    }

    /**
     * 通联报关查询
     * @param declareQryReqBody
     * @return
     */
    public DeclareQryRespBody declareQry(DeclareQryReqBody declareQryReqBody, ChannelMerchantInfoEntity entity){
        DeclareQryRespBody response = new DeclareQryRespBody();
        DeclareQryReq req = new DeclareQryReq();
        try {
            String declareKey = null == entity ? tongLianConfig.getDeclareKey() : entity.getDeclareKey();
            //1.设置请求头信息
            DeclareQryReqHead head = new DeclareQryReqHead();
            head.setCharset("1");
            head.setSignMsg("1");
            head.setSignType("1");
            head.setTransDatetime(DateUtils.get(8,new Date()));
            head.setVersion("v5.6");
            //先放入请求body
            req.setQryReqBody(declareQryReqBody);
            //2.签名
            String bodyXml = XmlUtil.toXml(declareQryReqBody);
            String sign = MD5Util.MD5Encode(bodyXml + "<key>" + declareKey + "</key>", "UTF-8").toUpperCase();
            //签名放到头部
            head.setSignMsg(sign);
            //签名完塞入head然后设置值
            req.setQryReqHead(head);
            //3.生成paramsValue
            String reqXml = XmlUtil.toXml(req);
            String param = Base64.encode(reqXml.getBytes(StandardCharsets.UTF_8));
            //base64编码中的加号替换成%2B
            param = param.replace("+", "%2B");
            LogUtil.info(logger,"报关查询请求报文【{0}】", reqXml);
            String result = doPost(tongLianConfig.getDeclareQryUrl(), param);
            //进行查询返回测试mock
            //String result = mockQryTest(req);
            LogUtil.info(logger,"报关查询请求返回结果【{0}】",result);
            DeclareQryResp resp = XmlUtil.toBean(result, DeclareQryResp.class);
            //验签
            String respSign = resp.getQryRespHead().getSignMsg();
            String respBodyXml = result.substring(result.indexOf("<BODY>"), result.indexOf("</BODY>")+7);
            String localSign = MD5Util.MD5Encode(respBodyXml + "<key>" + declareKey + "</key>", "UTF-8").toUpperCase();
            if(!respSign.equals(localSign)){
                throw new SystemException(ExceptionEnum.TL1010, "通联报关返回报文验签失败", TradeStatusEnum.UNKNOWN);
            }
            response = resp.getQryRespBody();
        }catch (Exception e){
            if(e instanceof SystemException){
                SystemException ex = (SystemException)e;
                response.setReturnCode(ex.getCode());
                response.setReturnMsg(ex.getMsg());
            }else{
                response.setReturnCode(ExceptionEnum.E10000.name());
                response.setReturnMsg(ExceptionEnum.E10000.getMsg());
            }
        }
        return response;
    }

    /**
     * 组请求头信息
     * @param channelNo
     * @return
     */
    private TlDeclareHead buildTlDeclareHead(String channelNo, String mchtId) {
        TlDeclareHead head = new TlDeclareHead();
        head.setVersion("v5.6");
        head.setVisitorId("MCT");
        head.setMchtId(mchtId);
        head.setOrderNo(channelNo);
        head.setTransDatetime(DateUtils.get(8,new Date()));
        head.setCharset("1");
        head.setSignType("1");
        return head;
    }

    /**
     * 发送 POST 请求（HTTP），K-V形式
     *
     * @param apiUrl API接口URL
     * @param data 参数map
     * @return 请求返回内容
     */
    private static String doPost(String apiUrl, String data){
        try {
            HttpResponse response = HttpUtil.form(apiUrl, "data=" + data);
            int code = response.statusCode();
            if (code != 200) {
                LogUtil.error(logger, "请求通联{0}接口返回非200code,response:{1}", "报关", response);
                throw new SystemException(ExceptionEnum.TL1009, "[通联HTTP]请求通联" + "报关" + "接口返回非200code", TradeStatusEnum.UNKNOWN);
            }
            String result = response.bodyText();
            if (StringUtils.isEmpty(result)) {
                LogUtil.error(logger, "请求通联{0}接口返回空报文,response:{1}", "报关", response);
                throw new SystemException(ExceptionEnum.TL1009, "[通联HTTP]请求通联" + "报关" + "接口返回空报文", TradeStatusEnum.UNKNOWN);
            }
            result = result.replace("%2B", "+");
            return new String(Base64.decode(result), StandardCharsets.UTF_8);
        } catch (Exception e) {
            LogUtil.error(logger, "通联报关http请求异常" + e.getMessage());
            throw new SystemException(ExceptionEnum.TL1009, TradeStatusEnum.UNKNOWN);
        }
    }

    @Getter
    @Setter
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JacksonXmlRootElement(localName = "PAYMENT_INFO")
    public static class PaymentInfoReq{

        @JacksonXmlProperty(localName = "HEAD")
        @JSONField(name = "HEAD")
        private TlDeclareHead headDto;

        @JacksonXmlProperty(localName = "BODY")
        @JSONField(name = "BODY")
        private TlDeclareHttpReq bodyReqDto;

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
        }
    }

    @Getter
    @Setter
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JacksonXmlRootElement(localName = "PAYMENT_INFO")
    public static class PaymentInfoResp{

        @JacksonXmlProperty(localName = "HEAD")
        @JSONField(name = "HEAD")
        private TlDeclareHead headDto;

        @JacksonXmlProperty(localName = "BODY")
        @JSONField(name = "BODY")
        private TlDeclareHttpResp bodyRespDto;
    }

    @Getter
    @Setter
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JacksonXmlRootElement(localName = "PAYMENT_INFO")
    public static class DeclareQryReq{

        @JacksonXmlProperty(localName = "HEAD")
        @JSONField(name = "HEAD")
        private DeclareQryReqHead qryReqHead;

        @JacksonXmlProperty(localName = "BODY")
        @JSONField(name = "BODY")
        private DeclareQryReqBody qryReqBody;
    }

    @Getter
    @Setter
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JacksonXmlRootElement(localName = "PAYMENT_INFO")
    public static class DeclareQryResp{

        @JacksonXmlProperty(localName = "HEAD")
        @JSONField(name = "HEAD")
        private DeclareQryRespHead qryRespHead;

        @JacksonXmlProperty(localName = "BODY")
        @JSONField(name = "BODY")
        private DeclareQryRespBody qryRespBody;
    }

    @Getter
    @Setter
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JacksonXmlRootElement(localName = "PAYMENT_INFO")
    public static class DeclareReSendReq{
        @JacksonXmlProperty(localName = "HEAD")
        @JSONField(name = "HEAD")
        private DeclareReSendHead reSendReqHead;

        @JacksonXmlProperty(localName = "BODY")
        @JSONField(name = "BODY")
        private DeclareReSendReqBody reSendReqBody;
    }

    @Getter
    @Setter
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JacksonXmlRootElement(localName = "PAYMENT_INFO")
    public static class DeclareReSendResp{
        @JacksonXmlProperty(localName = "HEAD")
        @JSONField(name = "HEAD")
        private DeclareReSendHead reSendRespHead;

        @JacksonXmlProperty(localName = "BODY")
        @JSONField(name = "BODY")
        private DeclareReSendRespBody reSendRespBody;
    }

    /**
     * 测试使用：模拟报关申请通联返回
     * @return
     */
    private String mockTest(PaymentInfoReq req){
        PaymentInfoResp resp = new PaymentInfoResp();
        TlDeclareHead head = new TlDeclareHead();
        head.setCharset("1");
        head.setMchtId(req.getHeadDto().getMchtId());
        head.setOrderNo(req.getHeadDto().getOrderNo());
        head.setSignType("1");
        head.setTransDatetime("20200222120000");
        head.setVersion("v5.6");
        head.setVisitorId("MCT");
        resp.setHeadDto(head);
        TlDeclareHttpResp respBody = new TlDeclareHttpResp();
        respBody.setReturnCode("0000");
        respBody.setReturnMsg("成功");
        respBody.setVerDept("1");
        resp.setBodyRespDto(respBody);
        String bodyXml = XmlUtil.toXml(respBody);
        String sign = MD5Util.MD5Encode(bodyXml + "<key>" + tongLianConfig.getDeclareKey() + "</key>", "UTF-8").toUpperCase();
        head.setSignMsg(sign);
        String respXml = XmlUtil.toXml(resp);
        System.out.println("模拟返回报文：" + respXml);
        String param = Base64.encode(respXml.getBytes(StandardCharsets.UTF_8));
        System.out.println("模拟返回Base64报文：" + param);
        //base64编码中的加号替换成%2B
        param = param.replace("+", "%2B");
        return respXml;
    }

    /**
     * 测试使用：模拟报关查询通联返回
     * @param req
     * @return
     */
    private String mockQryTest(DeclareQryReq req) {
        DeclareQryResp resp = new DeclareQryResp();
        DeclareQryRespHead retHead = new DeclareQryRespHead();
        retHead.setCharset(req.getQryReqHead().getCharset());
        retHead.setPaymentOrderNo(req.getQryReqBody().getPaymentOrderNo());
        retHead.setSignType(req.getQryReqHead().getSignType());
        retHead.setTransDatetime(req.getQryReqHead().getTransDatetime());
        retHead.setVersion(req.getQryReqHead().getVersion());
        resp.setQryRespHead(retHead);
        DeclareQryRespBody retBody = new DeclareQryRespBody();
        retBody.setReturnCode("0000");
        retBody.setReturnMsg("成功");
        retBody.setSendStatus("1");
        retBody.setInsReceiptStatus("1");
        retBody.setCustomsReceiptStatus("2");
        retBody.setSendDateTime("20200225121212");
        retBody.setInsReceiptTime("20200225121212");
        retBody.setCustomsReceiptTime("20200225121212");
        resp.setQryRespBody(retBody);
        String bodyXml = XmlUtil.toXml(retBody);
        String sign = MD5Util.MD5Encode(bodyXml + "<key>" + tongLianConfig.getDeclareKey() + "</key>", "UTF-8").toUpperCase();
        retHead.setSignMsg(sign);
        String respXml = XmlUtil.toXml(resp);
        System.out.println("模拟查询返回报文：" + respXml);
        String param = Base64.encode(respXml.getBytes(StandardCharsets.UTF_8));
        System.out.println("模拟查询返回Base64报文：" + param);
        //base64编码中的加号替换成%2B
        param = param.replace("+", "%2B");
        return respXml;
    }


    public static void main(String[] args) throws Exception{
        testDeclare();
        //testDeclareQry();
        //String xml = "<PAYMENT_INFO><HEAD><VERSION>v5.6</VERSION><TRANS_DATETIME>20200224075422</TRANS_DATETIME><CHARSET>1</CHARSET><SIGN_TYPE>1</SIGN_TYPE><SIGN_MSG>989E30F0EB51329345D15D7DD97DE8C1</SIGN_MSG><PAYMENT_ORDER_NO>12345678900</PAYMENT_ORDER_NO></HEAD><BODY><RETURN_CODE>0007</RETURN_CODE><RETURN_MSG>未找到原记录</RETURN_MSG></BODY></PAYMENT_INFO>";
        //System.out.println();
    }

    private static void testDeclareQry(){
        DeclareQryReq req = new DeclareQryReq();
        DeclareQryReqHead head = new DeclareQryReqHead();
        head.setCharset("1");
        head.setSignType("1");
        head.setSignMsg("1");
        head.setTransDatetime(DateUtils.get(8,new Date()));
        head.setVersion("v5.6");
        //先放入请求body
        DeclareQryReqBody reqBody = new DeclareQryReqBody();
        reqBody.setVisitorId("MCT");
        reqBody.setPaymentOrderNo("12345678900");
        reqBody.setPaymentMchtId("109123456123456");
        reqBody.setCustomsCode("HG023");
        reqBody.setMchtOrderNo("12345678990");
        req.setQryReqBody(reqBody);
        //2.签名
        String bodyXml = XmlUtil.toXml(reqBody);
        String sign = MD5Util.MD5Encode(bodyXml + "<key>1234567890</key>", "UTF-8").toUpperCase();
        //签名放到头部
        head.setSignMsg(sign);
        //签名完塞入head然后设置值
        req.setQryReqHead(head);
        //3.生成paramsValue
        String reqXml = XmlUtil.toXml(req);
        String param = Base64.encode(reqXml.getBytes(StandardCharsets.UTF_8));
        //base64编码中的加号替换成%2B
        param = param.replace("+", "%2B");
        LogUtil.info(logger,"报关查询请求报文【{0}】", reqXml);
        String result = doPost("http://ceshi.allinpay.com/customs/access", param);
        LogUtil.info(logger,"报关查询请求返回结果【{0}】",result);
        DeclareQryResp resp = XmlUtil.toBean(result, DeclareQryResp.class);
        //验签
        String respSign = resp.getQryRespHead().getSignMsg();
        String respBodyXml = result.substring(result.indexOf("<BODY>"), result.indexOf("</BODY>")+7);
        String localSign = MD5Util.MD5Encode(respBodyXml + "<key>1234567890</key>", "UTF-8").toUpperCase();
        if(!respSign.equals(localSign)){
            throw new SystemException(ExceptionEnum.TL1010, "通联报关返回报文验签失败", TradeStatusEnum.UNKNOWN);
        }
    }

    private static void testDeclare() throws Exception{
        PaymentInfoReq req = new PaymentInfoReq();
        TlDeclareHead headDto = new TlDeclareHead();
        headDto.setVersion("v5.6");
        headDto.setVisitorId("MCT");
        headDto.setMchtId("109123456123456");
        headDto.setOrderNo("20170701002004");
        headDto.setTransDatetime("20170601120816");
        headDto.setCharset("1");
        headDto.setSignType("1");
        TlDeclareHttpReq reqDto = new TlDeclareHttpReq();
        reqDto.setCustomsCode("HG014");
        reqDto.setPaymentChannel("2");
        reqDto.setCusId("109123456123456");
        reqDto.setPaymentDatetime("20170601120816");
        reqDto.setMchtOrderNo("NO20170705102740");
        reqDto.setPaymentOrderNo("T2017080945678999");
        reqDto.setPaymentAmount("10000");
        reqDto.setCurrency("156");
        reqDto.setEshopEntCode("123");
        reqDto.setBizTypeCode(" ");
        reqDto.setEshopEntName("name");
        reqDto.setPayerName("张三");
        reqDto.setPaperType("01");
        reqDto.setPaperNumber("453722198808098231");
        reqDto.setPaperPhone("13888888888");
        reqDto.setMemo("备注而已");
        String signKey = "1234567890";
        String xml = XmlUtil.toXml(req);
        String signXml = XmlUtil.toXml(reqDto);
        String url = "http://ceshi.allinpay.com/customs/pvcapply";
        //先放入请求body
        req.setBodyReqDto(reqDto);
        //2.签名
        String bodyXml = XmlUtil.toXml(reqDto);
        String sign = MD5Util.MD5Encode(bodyXml + "<key>1234567899</key>", "UTF-8").toUpperCase();
        headDto.setSignMsg(sign);
        //签名完塞入head然后设置值
        req.setHeadDto(headDto);
        //3.生成paramsValue
        System.out.println(XmlUtil.toXml(req));
        String param = Base64.encode(XmlUtil.toXml(req).getBytes(StandardCharsets.UTF_8));
        //base64编码中的加号替换成%2B
        param = param.replace("+", "%2B");
        Map<String, String> params = new HashMap<>();
        params.put("data", param);
        System.out.println(param);
        HttpResponse response = HttpUtil.form(url, "data=" + param);

        //String result = doPost(url, params);
        String result = response.bodyText();
        System.out.println(result);
        result = result.replace("%2B", "+");
        byte[] deResult = Base64.decode(result);
        String respXml = new String (deResult, "utf-8");
        System.out.println(respXml);
        PaymentInfoResp resp = XmlUtil.toBean(respXml, PaymentInfoResp.class);
        //验签
        String respSign = resp.getHeadDto().getSignMsg().toUpperCase();
        String respBodyXml = respXml.substring(respXml.indexOf("<BODY>"), respXml.indexOf("</BODY>")+7);
        String localSign = MD5Util.MD5Encode(respBodyXml + "<key>1234567890</key>", "UTF-8").toUpperCase();
        if(!respSign.equals(localSign)){
            throw new SystemException(ExceptionEnum.TL1010, "通联报关返回报文验签失败", TradeStatusEnum.UNKNOWN);
        }
        TlDeclareHead head = resp.getHeadDto();
        TlDeclareHttpResp resp1 = resp.getBodyRespDto();
        System.out.println(resp);
    }
}
